/*
 * This file is part of the URI Template library.
 *
 * For licensing information please see the file license.txt included in the release.
 * A copy of this licence can also be found at 
 *   http://www.opensource.org/licenses/artistic-license-2.0.php
 */
package org.weborganic.furi;

import java.util.Hashtable;
import java.util.Map;

/**
 * A convenience class is to bind variables to resolvers in a set of URI patterns.
 * 
 * Variables can be bound to a resolver by type or by name.
 * 
 * To assign a {@link VariableResolver} to variables of a specific type, use {@link #bindType(String, VariableResolver)}.
 * 
 * The following example will bind all variables typed <code>int</code> to return the corresponding integer value.
 * <pre>
 *   VariableBinder binder = new VariableBinder();
 *   b.bindType("int", new VariableResolver(){
 *     public boolean exists(String v) {return v.matches("\\d+");}
 *     public Integer resolve(String v) {return exists(v)? Integer.valueOf(v) : null;};
 *   });
 * </pre>
 * 
 * To assign a {@link VariableResolver} to variables of a specific name, use {@link #bindName(String, VariableResolver)}.
 *
 * The following example will bind all variables typed <code>int</code> to return the corresponding integer value.
 * <pre>
 *   VariableBinder binder = new VariableBinder();
 *   b.bindName("name", new VariableResolver(){
 *     public boolean exists(String v) {return true;}
 *     public Integer resolve(String v) {return exists(v)? Integer.valueOf(v) : null;};
 *   });
 * </pre>
 * 
 * 
 * @author Christophe Lauret
 * @version 11 June 2009
 */
public class VariableBinder {

  /**
   * The default resolver accepts everything and resolves all values to themselves.
   */
  private final static VariableResolver DEFAULT_RESOLVER = new VariableResolver() {
    public boolean exists(String value) { return true; }
    public Object resolve(String value) { return value; }
  };

  /**
   * Maps a variable names to a resolver.
   */
  private Map<String,VariableResolver> _byname = new Hashtable<String,VariableResolver>();

  /**
   * Maps a variable types to a resolver.
   */
  private Map<String,VariableResolver> _bytype = new Hashtable<String,VariableResolver>();

  /**
   * Binds the variables with the specified name to the specified resolver.
   * 
   * @deprecated use #bindName() or #bindType() instead
   * 
   * @param name     The name of the variable.
   * @param resolver The resolver to use with these variables.
   */
  public void bind(String name, VariableResolver resolver) {
    this._byname.put(name, resolver);
  }

  /**
   * Binds the variables with the specified name to the specified resolver.
   * 
   * @param name     The name of the variable.
   * @param resolver The resolver to use with these variables.
   */
  public void bindName(String name, VariableResolver resolver) {
    this._byname.put(name, resolver);
  }

  /**
   * Binds the variables with the specified name to the specified resolver.
   * 
   * @param type     The variable type.
   * @param resolver The resolver to use with these variables.
   */
  public void bindType(String type, VariableResolver resolver) {
    this._bytype.put(type, resolver);
  }

  /**
   * Returns the resolver used for the variable of the specified name or type.
   * 
   * <p>By default, looks for the resolver assigned to the specified variable name; if no resolver
   * is bound to the variable name, it will return the resolver bound to the given variable type.
   * 
   * <p>This method does not return <code>null</code>. If the specified variable name or type is not
   * bound to any resolver the default resolver if returned instead.
   * 
   * @param name The name of the variable.
   * @param type The type of the variable.
   * 
   * @return the corresponding resolver.
   */
  public VariableResolver getResolver(String name, VariableType type) {
    VariableResolver resolver = this._byname.get(name);
    // try to find a resolver by type
    if (resolver == null && type != null)
      resolver = this._bytype.get(type.getName());
    // fall back on the default otherwise 
    return resolver != null? resolver : DEFAULT_RESOLVER;
  }

  /**
   * Returns the resolver used for the variable of the specified name.
   * 
   * <p>This method does not return <code>null</code>. If the specified variable name is no bound
   * to any resolver the default resolver if returned instead.
   * 
   * @param name The name of the variables.
   * 
   * @return the corresponding resolver.
   */
  public VariableResolver getResolver(String name) {
    VariableResolver resolver = this._byname.get(name);
    return resolver != null? resolver : DEFAULT_RESOLVER;
  }

  /**
   * Returns the resolver used for the variable of the specified type.
   * 
   * <p>This method does not return <code>null</code>. If the specified variable name is no bound
   * to any resolver the default resolver if returned instead.
   * 
   * @param type The type of the variable.
   * 
   * @return the corresponding resolver.
   */
  public VariableResolver getResolver(VariableType type) {
    if (type == null) return DEFAULT_RESOLVER;
    VariableResolver resolver = this._bytype.get(type.getName());
    return resolver != null? resolver : DEFAULT_RESOLVER;
  }

  /**
   * Indicates whether the given variable name is bound to a VariableResolver.
   * 
   * @param name The variable name.
   * 
   * @return <code>true</code> if a given variable resolver is bound to the specific name;
   *         <code>false</code> otherwise (including if the name is <code>null</code>. 
   */
  public boolean isNameBound(String name) {
    if (name == null) return false;
    return this._byname.containsKey(name);
  }

  /**
   * Indicates whether the given variable type is bound to a VariableResolver.
   * 
   * @param type The variable type.
   * 
   * @return <code>true</code> if a given variable resolver is bound to the specific type;
   *         <code>false</code> otherwise (including if the type is <code>null</code>. 
   */
  public boolean isTypeBound(String type) {
    if (type == null) return false;
    return this._bytype.containsKey(type);
  }

}
